using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Text.RegularExpressions;

namespace DBSourceToolsLib.SysObjects
{
    /// <summary>
    /// search for a string in an array of strings
    /// </summary>
    /// <remarks>
    /// Author: Peter Gabris
    /// peter@bsp-software.com
    /// Feb 2010
    /// (C) 1020 GNU License as part of DBSourceTools by Nathan Rosental
    /// see http://dbsourcetools.codeplex.com/
    /// </remarks>
    public class SearchEngine
    {
        public string SearchTerm { get; set; }
        public bool MatchTheWholeWord { get; set; }
        public bool SearchInComments { get; set; }
        public bool SearchInStrings { get; set; }
        private bool searchEverything;

        private enum SearchStatus{ Regular, InComment, InString};
        private SearchStatus status;
        private int searchedRowIndex;
        private List<SearchResultLine> found = new List<SearchResultLine>();
        private int nestedCommentLevel;

        // regular expressions
        private Regex rxDelimiter;
        private Regex rxSought;
        // the string end is either ' not followed by ' or ' at the end of the row
        private Regex rxStringEnd = new Regex( "'[^']|'$",
                RegexOptions.Compiled | RegexOptions.Singleline );
        private Regex rxCommentUncomment = new Regex( @"/\*|\*/",
                RegexOptions.Compiled | RegexOptions.Singleline );

        public SearchEngine( string SearchTerm,
                bool MatchTheWholeWord, bool SearchInComments, bool SearchInStrings )
        {
            this.SearchTerm = SearchTerm;
            this.MatchTheWholeWord = MatchTheWholeWord;
            this.SearchInComments = SearchInComments;
            this.SearchInStrings = SearchInStrings;
            searchEverything = ( SearchInComments && SearchInStrings );

            // build the search regex
            string sought = Regex.Escape( SearchTerm );
            StringBuilder rx = new StringBuilder( 500 );
            if ( MatchTheWholeWord )
            {
                // word boundary, start or the end of the row
                rx.Append( @"(^|\b)" + sought + @"(\b|$)");
            }
            else
            {
                rx.Append( sought );
            }
            rxSought = new Regex( rx.ToString(),
                        RegexOptions.Compiled
                        | RegexOptions.IgnoreCase
                        | RegexOptions.Singleline );

            // build the delimiter regex
            if ( !searchEverything )
            {
                rx = new StringBuilder( 500 );
                if ( !SearchInComments ) rx.Append( @"/\*|--" );
                bool searchCodeOnly = ( !SearchInComments && !SearchInStrings );
                if ( searchCodeOnly ) rx.Append( "|" );
                if ( !SearchInStrings ) rx.Append( "'" );
                rxDelimiter = new Regex( rx.ToString(),
                    RegexOptions.Compiled | RegexOptions.Singleline );
            }
        }

        /// <summary>
        /// uses the same criteria for each new search
        /// </summary>
        /// <param name="rows"></param>
        /// <returns></returns>
        public List<SearchResultLine> Search(StringCollection rows)
        {
            found.Clear();
            status = SearchStatus.Regular;

            for ( searchedRowIndex = 0; searchedRowIndex < rows.Count; searchedRowIndex++ )
            {
                string row = rows[ searchedRowIndex ];
                switch ( status )
                {
                    case SearchStatus.Regular:
                        // look for the first of opening comment or string
                        GeneralSearch( row, 0 );
                        break;
                    case SearchStatus.InComment:
                        // look for the comment terminator
                        CommentSearch( row, 0 );
                        break;
                    case SearchStatus.InString:
                        // look for the string terminator
                        StringSearch( row, 0 );
                        break;
                }
            }
            return found;
        }

        private void GeneralSearch(string term, int startAt )
        {
            if ( searchEverything )
            {
                SimpleSearch( term, 0 );
            }
            else
            {
                Match match = rxDelimiter.Match( term, startAt );
                if ( match.Success )
                {
                    string delimiter = match.Value;
                    int matchPosition = match.Index;
                    // search the head of the row
                    SimpleSearch( term.Substring(0, matchPosition), startAt );

                    // change the search status and verify if it is changed in the same row
                    switch ( delimiter )
                    {
                        case "/*":
                            status = SearchStatus.InComment;
                            nestedCommentLevel = 1;
                            CommentSearch( term, matchPosition + 2 );
                            break;
                        case "'":
                            status = SearchStatus.InString;
                            StringSearch( term, matchPosition + 1 );
                            break;
                        case "--":
                            // the rest of the row can be safely ignored
                            break;
                    }
                }
                else
                {
                    // no comment nor string, search the whole row
                    SimpleSearch( term, startAt );
                }
            }
        }

        private void CommentSearch( string term, int startAt )
        {
            // handle nested comments, seach for */ at nestedCommentLevel = 0
            while ( nestedCommentLevel > 0 )
            {
                Match match = rxCommentUncomment.Match( term, startAt );
                if ( match.Success )
                {
                    switch ( match.Value )
                    {
                        case "/*":
                            nestedCommentLevel++;
                            break;
                        case "*/":
                            nestedCommentLevel--;
                            break;
                    }
                    startAt = match.Index + 2;
                }
                else
                {
                    // no more comment brackets in the row, the level is still higher than 0
                    return; // >>>>>
                }
            }
            // process the tail as a regular row
            status = SearchStatus.Regular;
            GeneralSearch( term, startAt );
            return; // >>>>>
        }

        private void StringSearch( string term, int startAt )
        {
            // search for ' (not''), process the tail as a regular row if found
            Match match = rxStringEnd.Match( term, startAt );
            if ( match.Success )
            {
                status = SearchStatus.Regular;
                GeneralSearch( term, match.Index + 1 );
                return;
            }
            return;
        }

        private void SimpleSearch(string term, int startAt )
        {
            if( rxSought.IsMatch( term, startAt) )
            {
                SearchResultLine oResultLine = new SearchResultLine( searchedRowIndex + 1, term );
                found.Add( oResultLine );
                return;
            }
            return;
        }
    }
}
